iT邦幫忙

2022 iThome 鐵人賽

DAY 28
0
Modern Web

Hello TypeScript 菜鳥系列 第 28

Day 27. TypeScript Type Manipulation:Conditional Types

  • 分享至 

  • xImage
  •  

Conditional types很有意思,因為TypeScript有些很好用的utility types其實是用conditional types定義出來的。

但這是菜鳥系列,所以這篇不會證明如何用conditional types定義utility types,有興趣的人可以參考這篇文章,而這裡還是乖乖從TypeScript官方文件開始入手吧。


Condition types的語法非常類似於JavaScript的三元運算子(ternary operator),語法如下:

type ConditionType = T extends U ? X : Y;

若T型別是從U型別延伸(extends)出來的,則ConditionType會是X型別;否則的話,ConditionType會是Y型別。

例如:

interface Animal {
	specie: string;
	w: number;
	h: number;
}

interface Human extends Animal {
	name: string;
	age: number;
}

interface Flower {
	variety: string;
	color: string;
}

type H = Human extends Animal ? string : unknown;	// type H = string

type F = Flower extends Animal ? string : unknown;	// type F = unknown

但像上面這樣的hard code實在無法顯示出conditional types的強大,先將第一個範例改寫成generic形式:

type isHuman<T> = T extends Human? Human : unknown;

const xiaMing: Human = {specie: "human", w: 70, h: 175, name: 'xia ming', age: 25};

type Ming = isHuman<typeof xiaMing>;	// type Ming = Human

寫成generic形式之後,可以嘗試將conditional types和其他關鍵字、運算子或是取得型別的方式,例如和昨天學的indexed accessed types一起使用:

type Str<T> = T extends {name: string}? T["name"] : never;

const xiaMing = {specie: "human", w: 70, h: 175, name: 'xia ming', age: 25};

type M = Str<typeof xiaMing>;	// type M = string


或是加入 infer 關鍵字去推導型別,讓conditional types的使用方式更彈性:

// ex1.
type arrElem<T> = T extends Array<infer Elem> ? Elem : never;

type N1 = arrElem<Array<number>>;	// type N1 = number
type N2 = arrElem<number[]>;		// type N2 = number

type Never = arrElem<number>;		// type N3 = never


// ex2.
type getReturn<T> = T extends (...args: any) => infer Return ? Return : never;

type num = getReturn<()=>number>;


此外conditional types的語法也可以像三元運算子一樣,將多個conditional types串聯起來:

type WhatT<T> = T extends Human ? Human : T extends Animal ? Animal : never;

const dog = {specie: "dog", w: 28, h: 30};

type D = WhatT<typeof dog>;	// type D = Animal


最後要認識conditional types的 distributive 特性,而最一開始說的utility types就是利用這個特性實作出來的。

先回到最基本的conditional types語法,distributive會發生在型別T是union的時候:

type ConditionType = T extends U ? X : Y;

來看文件的例子,測試一下當型別T是union types的時候會發生什麼事,:

type UnionT<T> = T extends any ? T[] : never;

type U = UnionT<string | number>;	// type U = "string"[] | "number"[]

// 也就是
const stringArr: U = ["string", "string"];  // ok
const numberArr: U = ["number", "number"];  // ok

型別U會是 string[] | number[] 的原因在於TypeScript會把conditional types變成是 "union" 的 conditional types,意思是:

type U = UnionT<string | number> extends any ? T[] : never;
	   = UnionT<string> extends any | UnionT<number> extends any ? T[] : never;
	   = "string"[] | "number"[]

因為 UnionT<"string">UnionT<"number"> 各自確實是 any 型別的子集,所以compiler會判斷為true,也就因此得到 "string"[] 或者 "number"[] 型別。

但如果是希望得到的是 [string | number] 型別,需要將T型別加上 [],才能避開distributive:

type UnionT<T> = [T] extends any ? T[] : never;

type U = UnionT<string | number>;	// type U = string[] | number[]


const arr = [1, 2, 3, "hello", "world"];	// ok

同樣的,另一種array寫法也是要加上 Array<>

type UnionT<T> = Array<T> extends any ? Array<T> : never;

type U = UnionT<string | number>;

const arr = [1, 2, 3, "hello", "world"];	// ok

以上方式讓compiler知道要處理的是陣列元素的型別,也就能取得預期中的型別了~


參考資料
Conditional Types @TypeScript Handbook
Using TypeScript Conditional Types Like a Pro
Power of conditional types in Typescript


上一篇
Day 26. TypeScript Type Manipulation:Indexed Accessed Types
下一篇
Day 28. TypeScript Type Manipulation:Template Literal Types
系列文
Hello TypeScript 菜鳥31
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言